繼上一篇介紹Redis基本操作與實務,本次將討論Python/Django如何與Redis整合,提升系統效能。
Redis server安裝後,接著安裝Python套件redis.py:
pip install redis
範例1. redis.py簡單測試,程式名稱為23\redis_test.ipynb。
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
r.set('key1', 'value1')
r.get('key1')
r.delete('key1')
r.setex("important_key", 100, "important_value")
r.lpush('bikes:repairs', 'bike:1', 'bike:2')
r.lpop('bikes:repairs')
r.hset('user-session:123', mapping={
'name': 'John',
"surname": 'Smith',
"company": 'Redis',
"age": 29
})
{'name': 'John', 'surname': 'Smith', 'company': 'Redis', 'age': '29'}
dict_data = {
"employee_name": "Adam Adams",
"employee_age": 30,
"position": "Software Engineer",
}
r.mset(dict_data)
r.mget("employee_name", "employee_age", "position", "non_existing")
['Adam Adams', '30', 'Software Engineer', None]
使用程式可允許用戶端快取(Client-side Cache),即將Cache放在本機,可減少網路的延遲。
範例2. 用戶端快取測試,程式名稱為23\client_side_cache.ipynb。
import redis
from redis.cache import CacheConfig
r = redis.Redis(
protocol=3,# RESP3 protocol
cache_config=CacheConfig(),# enable client-side caching
decode_responses=True
)
redis-cli
127.0.0.1:6379> monitor
r.set("city", "New York")
cityNameAttempt1 = r.get("city") # Retrieved from Redis server and cached
cityNameAttempt2 = r.get("city") # Retrieved from cache
cache = r.get_cache()
cache.delete_by_redis_keys(["city"]) # 刪除 "city" cache
cache.flush() # 刪除所有的 cache
r.delete("city")
若快取的內容很多,可以建立索引(Index),Redis Stack支援JSON文件的索引。
Redis Stack是標準Redis server的強化版,支援向量資料型別的操作,主要是加強深度學習的嵌入(Embedding)向量儲存與運算。
Redis Stack安裝可參閱【Install Redis Stack on Linux】,啟動及關閉Server指令如下:
sudo systemctl start redis-stack-server # 啟動Server
sudo systemctl stop redis-stack-server # 關閉Server
範例3. 索引測試,程式名稱為23\index_test.ipynb。
import redis
from redis.commands.json.path import Path
import redis.commands.search.aggregation as aggregations
import redis.commands.search.reducers as reducers
from redis.commands.search.field import TextField, NumericField, TagField
from redis.commands.search.indexDefinition import IndexDefinition, IndexType
from redis.commands.search.query import NumericFilter, Query
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
user1 = {
"name": "Paul John",
"email": "paul.john@example.com",
"age": 42,
"city": "London"
}
user2 = {
"name": "Eden Zamir",
"email": "eden.zamir@example.com",
"age": 29,
"city": "Tel Aviv"
}
user3 = {
"name": "Paul Zamir",
"email": "paul.zamir@example.com",
"age": 35,
"city": "Tel Aviv"
}
schema = (
TextField("$.name", as_name="name"),
TagField("$.city", as_name="city"),
NumericField("$.age", as_name="age")
)
rs = r.ft("idx:users")
rs.create_index(
schema,
definition=IndexDefinition(
prefix=["user:"], index_type=IndexType.JSON
)
)
r.json().set("user:1", Path.root_path(), user1)
r.json().set("user:2", Path.root_path(), user2)
r.json().set("user:3", Path.root_path(), user3)
res = rs.search(
Query("Paul @age:[20 50]")
)
res
Result{2 total, docs: [Document {'id': 'user:3', 'payload': None, 'json': '{"name":"Paul Zamir","email":"paul.zamir@example.com","age":35,"city":"Tel Aviv"}'}, Document {'id': 'user:1', 'payload': None, 'json': '{"name":"Paul John","email":"paul.john@example.com","age":42,"city":"London"}'}]}
req = aggregations.AggregateRequest("*").group_by('@city', reducers.count().alias('count'))
print(rs.aggregate(req).rows)
[['city', 'London', 'count', '1'], ['city', 'Tel Aviv', 'count', '2']]
可進一步參閱【Vector Similarity】範例,它包含以下功能:
更多的範例可參閱【redis.py Examples】。
Django的cache機制可參閱【Django’s cache framework】,提供非常多層次的cache,包括:
Django支援多種cache server:
範例4. 測試Redis cache server,複製16\mysite專案,程式目錄為23\mysite。
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
}
}
MIDDLEWARE = [
# cache server
"django.middleware.cache.UpdateCacheMiddleware",
"django.middleware.cache.FetchFromCacheMiddleware",
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://username:password@127.0.0.1:6379",
}
}
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": [
"redis://127.0.0.1:6379", # leader
"redis://127.0.0.1:6378", # read-replica 1
"redis://127.0.0.1:6377", # read-replica 2
],
}
}
測試:在未啟動redis時,瀏覽網頁,會出現redis無法連線的錯誤。
啟動redis後測試:使用下列指令查看keys,可看到Django寫入的keys。
redis-cli
127.0.0.1:6379> KEYS *
1) ":1:views.decorators.cache.cache_header..78272bd4f1f319fe5e53cb2e55b3bfc6.en-us.UTC"
2) ":1:views.decorators.cache.cache_page..GET.78272bd4f1f319fe5e53cb2e55b3bfc6.18481c8d117eb338db00f2e7c6858469.en-us.UTC"
from django.views.decorators.cache import cache_page
...
@login_required()
@cache_page(60 * 3, key_prefix="mysite")
def vote_summary(request, poll_id):
# 投票
http://localhost:8000/vote/1
# 投票統計
http://localhost:8000/poll/summary/1/
1) ":1:views.decorators.cache.cache_header..78272bd4f1f319fe5e53cb2e55b3bfc6.en-us.UTC"
2) ":1:views.decorators.cache.cache_page..GET.78272bd4f1f319fe5e53cb2e55b3bfc6.18481c8d117eb338db00f2e7c6858469.en-us.UTC"
這個技能在大部份的企業網站都可以應用的上,例如顯示當日新聞、重大訊息揭露或每日股價顯示都可以顯著提升系統效能。
Redis cache server雖然很穩定,也提供List支援Queue的操作,但是,筆者在關鍵的應用程式中,為了不遺漏任何交易記錄,還是選擇速度較慢的關聯式資料庫,記錄大量的手機開通交易,主要的考量是不可遺漏任何一筆未完成的交易,免得烏紗帽落地,事後證明以多執行緒配合,速度還是夠快,足以處理來自全國各地的訂單,相對的,在另一個系統需要大量發送通知(Info)的訊息,筆者就毫不遲疑地使用Redis,因為,漏送通知訊息,問題不大,只要事後補送即可,舉這兩個實例,只是要說明,有時候專案經理或架構師必須在效能與正確性之間作抉擇。
本系列的程式碼會統一放在GitHub,本篇的程式放在src/23資料夾,歡迎讀者下載測試,如有錯誤或疏漏,請不吝指正。